package com.xunfang.portal.config;

import cn.hutool.crypto.digest.MD5;
import cn.hutool.json.JSONUtil;
import com.xunfang.common.enumeration.impl.BusinessCode;
import com.xunfang.common.exception.Assert;
import com.xunfang.portal.annotion.Idempotent;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

/**
 * 切面类，只要方法上有自定义注解：@Idempotent，这个方法就做幂等校验
 *    切点(切点表达式) + 通知 (增强代码)
 *
 */
@Component
@Aspect
@Slf4j
public class IdempotentAspect {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 使用环绕通知
     *
     * @annotation(idempotent) 和 方法形参名一样
     */
    @Around("@annotation(idempotent)")
    public Object handler(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
        // 获取方法参数，得到参数数据
        Object[] args = joinPoint.getArgs();
        // 将参数转成JSON字符串，相同参数的请求只能执行一次
        String param = JSONUtil.toJsonStr(args);
        // 参数MD5加密，保证键的唯一性，作为Redis的key
        String key = MD5.create().digestHex(param);
        log.info("幂等参数：{} 幂等key：{}", param, key);
        // 幂等校验，判断Redis中唯一标识是否已经存在，如果存在则直接返回。键：唯一标识，值：1， 20秒后过期
        Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, 1, idempotent.expire(), idempotent.timeUnit());
        //如果返回true，表示设置成功，如果返回false，表示已经提交过，返回错误
        Assert.error(!ifAbsent, BusinessCode.REPEAT_COMMIT);
        // 校验通过：放行
        return joinPoint.proceed();
    }
}